package com.j2data.spike.controller;


import com.j2data.spike.config.AccessLimit;
import com.j2data.spike.config.RedisConfig;
import com.j2data.spike.exception.GlobalException;
import com.j2data.spike.pojo.Goods;
import com.j2data.spike.pojo.SpikeMessage;
import com.j2data.spike.pojo.SpikeOrder;
import com.j2data.spike.pojo.User;
import com.j2data.spike.rabbitmq.MQSender;
import com.j2data.spike.service.IGoodsService;
import com.j2data.spike.service.IOrderService;
import com.j2data.spike.service.ISpikeOrderService;
import com.j2data.spike.utils.JsonUtil;
import com.j2data.spike.vo.GoodsVo;
import com.j2data.spike.vo.RespBean;
import com.j2data.spike.vo.RespBeanEnum;
import com.wf.captcha.ArithmeticCaptcha;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.TimeUnit;


/**
 * 秒杀处理类
 *
 * @author j2data
 * @version 1.0
 */
@Controller
@RequestMapping("/spike")
@Slf4j
public class SpikeController implements InitializingBean {

    @Autowired
    private IGoodsService goodsService;

    @Autowired
    private ISpikeOrderService spikeOrderService;

    @Autowired
    private IOrderService orderService;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private RedisScript<Long> script;
    @Autowired
    private MQSender mqSender;

    private HashMap<Long, Boolean> emptyStockMap = new HashMap<>();

//    /**
//     * 秒杀
//     * windows优化前QPS：1626.5
//     * 缓存优化QPS：1872
//     * linux优化前QPS：
//     *
//     * @param model
//     * @param user
//     * @param goodsId
//     * @return
//     */
//    @RequestMapping("/doSpike")
//    public String doSpike(Model model, User user, Long goodsId) {
//        if (user == null) {
//            return "login";
//        }
//        model.addAttribute("user", user);
//        GoodsVo goodsVo = goodsService.findGoodsVoByGoodsId(goodsId);
//        //判断库存
//        if (goodsVo.getStockCount() < 1) {
//            model.addAttribute("errmsg", RespBeanEnum.EMPTY_ERROR.getMessage());
//            return "spikeFail";
//        }
//        //判断是否重复抢购
//        SpikeOrder spikeOrder  = (SpikeOrder)redisTemplate.opsForValue()
//                .get("order:" + user.getId() + ":" + goodsVo.getId());
//
//        if (spikeOrder != null) {
//            model.addAttribute("errmsg",RespBeanEnum.REPLATE_ERROR.getMessage());
//            return "spikeFail";
//        }
//
//        spikeOrder = orderService.spike(user,goodsVo);
//        model.addAttribute("order",spikeOrder);
//        model.addAttribute("goods",goodsVo);
//        if (spikeOrder != null) {
//            //判断秒杀成功后才放入redis
//            redisTemplate.opsForValue().set("order:"+user.getId() + ":"+goodsVo.getId(), spikeOrder,60,TimeUnit.SECONDS);
//
//        }
//        return "orderDetail";
//    }

    /**
     * 获取秒杀地址
     *
     * @param user
     * @param goodsId
     * @return
     */
    @AccessLimit(second = 5,maxCount = 5,needLogin = true)
    @ResponseBody
    @RequestMapping(value = "/path", method = RequestMethod.GET)
    public RespBean getPath(User user, Long goodsId,String captcha) {
        if (user == null) {
            return RespBean.error(RespBeanEnum.SESSION_ERROR);
        }

        Boolean check = orderService.checkCaptcha(user,goodsId, captcha);
        if (!check) {
            return RespBean.error(RespBeanEnum.ERROR_CAPTCHA);
        }
        String path = orderService.createPath(user, goodsId);
        return RespBean.success(path);
    }

    /**
     * 秒杀
     * windows优化前QPS：1626.5
     * 缓存QPS：1347
     * 优化QPS：2452
     * linux优化前QPS：
     *
     * @param user
     * @param goodsId
     * @return
     */
    @RequestMapping(value = "/{path}/spike", method = RequestMethod.POST)
    @ResponseBody
    public RespBean spike(@PathVariable String path, User user, Long goodsId) {

        if (user == null) {
            return RespBean.error(RespBeanEnum.SESSION_ERROR);
        }
        Boolean check = orderService.checkPath(user, goodsId, path);
        if (!check) {
            return RespBean.error(RespBeanEnum.REQUEST_ILLEGAL);
        }
        //从redis中去判断是否重复抢购
        SpikeOrder spikeOrder = (SpikeOrder) redisTemplate.opsForValue().get("order:" + user.getId() + ":" + goodsId);

        if (spikeOrder != null) {
            return RespBean.error(RespBeanEnum.REPLATE_ERROR);
        }
        //内存标记，减少redis访问
        if (emptyStockMap.get(goodsId)) {
            return RespBean.error(RespBeanEnum.EMPTY_ERROR);
        }
        //lua预减库存
        Long stock = ((Long) redisTemplate.execute(script, Collections.singletonList("spikeGoods:" + goodsId), Collections.EMPTY_LIST));
        if (stock < 0) {
            emptyStockMap.put(goodsId, true);
            return RespBean.error(RespBeanEnum.EMPTY_ERROR);
        }

        SpikeMessage spikeMessage = new SpikeMessage(user, goodsId);
        mqSender.sendSpikeMessage(JsonUtil.object2JsonStr(spikeMessage));
        //Order order = orderService.spike(user, goodVo);
        return RespBean.success(0);
        /*
        GoodsVo goodsVo = goodsService.findGoodsVoByGoodsId(goodsId);
        //判断库存
        if (goodsVo.getStockCount() < 1) {
            return RespBean.error(RespBeanEnum.EMPTY_ERROR);
        }
        //从redis中去判断是否重复抢购
        SpikeOrder spikeOrder  = (SpikeOrder)redisTemplate.opsForValue()
                .get("order:" + user.getId() + ":" + goodsVo.getId());
        if (spikeOrder != null) {
            return RespBean.error(RespBeanEnum.REPLATE_ERROR);
        }

        spikeOrder = orderService.spike(user,goodsVo);
        //判断秒杀成功后才放入redis
        if (spikeOrder == null) {
            return RespBean.error(RespBeanEnum.EMPTY_ERROR);
        }
        redisTemplate.opsForValue().set("order:"+user.getId() + ":"+goodsVo.getId(), spikeOrder,60,TimeUnit.SECONDS);
        return RespBean.success(spikeOrder);
        */

    }

    /**
     * 验证码
     *
     * @param user
     * @param goodsId
     * @param response
     */
    @RequestMapping(value = "/captcha", method = RequestMethod.GET)
    public void verifyCode(User user, Long goodsId, HttpServletResponse response) {
        if (user == null || goodsId < 0) {
            throw new GlobalException(RespBeanEnum.REQUEST_ILLEGAL);
        }

        // 设置请求头为输出图片类型
        response.setContentType("image/jpg");
        response.setHeader("Pragma", "No-cache");
        response.setHeader("Cache-Control", "no-cache");
        response.setDateHeader("Expires", 0);
        //生成验证码，将结果放入redis
        ArithmeticCaptcha captcha = new ArithmeticCaptcha(130, 32, 3);
        redisTemplate.opsForValue().set("captcha:" + user.getId() + ":" + goodsId, captcha.text
                (), 300, TimeUnit.SECONDS);
        try {
            captcha.out(response.getOutputStream());
        } catch (IOException e) {
            log.error("验证码生成失败", e.getMessage());
        }
    }

    /**
     * 获取秒杀结果
     *
     * @param user
     * @param goodsId
     * @return orderId:成功;-1:失败;0:排队中
     */
    @RequestMapping(value = "/result", method = RequestMethod.GET)
    @ResponseBody
    public RespBean result(User user, Long goodsId) {
        if (user == null) {
            return RespBean.error(RespBeanEnum.SESSION_ERROR);
        }
        Long orderId = spikeOrderService.getResult(user, goodsId);
        return RespBean.success(orderId);
    }

    /**
     * 系统初始化，将库存信息存入redis中
     *
     * @throws Exception
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        List<GoodsVo> list = goodsService.findGoodsVo();
        if (CollectionUtils.isEmpty(list)) {
            return;
        }
        list.forEach(goodsVo -> {
            redisTemplate.opsForValue().set("spikeGoods:" + goodsVo.getId(), goodsVo.getStockCount());
            emptyStockMap.put(goodsVo.getId(), false);
        });

    }
}
